;***********************************************
;nasm loader.asm -o LOADER.BIN
;loader.bin的大小限制为4KB
;***********************************************
org 0100h;必须为0100h,因为boot.asm中跳转来时cs为09000h,保证地址偏移正确
SectorsOfMBR				equ 63		;MBR 大小
BaseOfFat					equ 07900h
BaseOfKernel				equ	08000h	; KERNEL.BIN 被加载到的位置 ----  段地址
OffsetOfKernel				equ	 0100h	; KERNEL.BIN 被加载到的位置 ----  偏移地址
BaseOfLoader				equ	09000h	; LOADER.BIN 被加载到的位置 ----  段地址
OffsetOfLoader				equ	0100h	; LOADER.BIN 被加载到的位置 ----  偏移地址
BaseOfKernelFilePhyAddr		equ	 BaseOfKernel*10h + OffsetOfKernel

BaseOfStack					equ	0100h	; 堆栈基址

; =============  描述符 ===================================
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
	dw	%2 & 0FFFFh							; 段界限1
	dw	%1 & 0FFFFh							; 段基址1
	db	(%1 >> 16) & 0FFh					; 段基址2
	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 属性1 + 段界限2 + 属性2
	db	(%1 >> 24) & 0FFh					; 段基址3
%endmacro 						; 共 8 字节

DA_DRW		EQU	92h		; 存在的可读写数据段属性值
DA_32		EQU	4000h	; 32 位段
DA_C		EQU	98h		; 存在的只执行代码段属性值
DA_CR		EQU	9Ah		; 存在的可执行可读代码段属性值
DA_DPL3		EQU	  60h	; DPL = 3
SA_RPL3		EQU	3		; 
DA_LIMIT_4K	EQU	8000h	; 段界限粒度为 4K 字节
;===========================================================
struc disk_addr_pkt
	packet_size:  		resb  1  ; 磁盘参数包的尺寸，必须为10H
	reserved:    		resb  1  ;   保留，必须为零
	block_count:  		resw  1  ;   传输的扇区数
	buffer_addr: 		resd  1  ;   内存缓冲区地址（段：偏移）
	block_num:    		resq  1    ;   起始绝对扇区号（即起始扇区的LBA号码）
endstruc


;;**********************  Entry ********************************
;;**********************        ********************************
	jmp short START
; 下面是 FAT32 磁盘的头
%include "fat32head.inc"
;offset 90Bytes

[section .begin]
[BITS 16]
START:
	pop ax			;BS_DrvNum
	mov byte[BS_DrvNum], al
	mov ax, cs
	mov es, ax
	mov ds, ax
	mov ss, ax
	mov	sp, BaseOfStack
	
;;得到内存情况
		mov	ebx, 0
		mov di, _MemChkBuf
.loop:
		mov	eax, 0E820h
		mov ecx, 20
		mov edx, 0534D4150h
		int 15h
		jc	LABEL_MEM_CHK_FAIL
		add di, 20
		inc	dword[_dwMCRNumber]
		cmp	ebx, 0
		jne	.loop
		jmp	LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
		mov dword [_dwMCRNumber], 0
LABEL_MEM_CHK_OK:	
	
	; 清屏
	mov	ax, 0600h		; AH = 6,  AL = 0h
	mov	bx, 0700h		; 黑底白字(BL = 07h)
	mov	cx, 0			; 左上角: (0, 0)
	mov	dx, 0184fh		; 右下角: (80, 50)
	int	10h				; int 10h
	;;Loading
	mov ax, _KernelLoading
	call DispStr
;;计算根目录扇区位置
		
	xor eax, eax
	mov al, [BPB_NumFATs]
	mov ebx, [BPB_SecPerFat]
	mul ebx
	xor ebx, ebx
	mov bx, word[BPB_RsvdSecCnt]
	add eax, ebx
	mov dword[_SectorNoOfRootDirectory], eax
	add eax, SectorsOfMBR ;;sectors of MBR
		;;读取根目录两个扇区
	push eax
	push 2
	push OffsetOfKernel
	push BaseOfKernel
	call ReadSector
	add  sp , 10
	
	push es
	mov ax, BaseOfKernel
	mov es, ax
	mov di, OffsetOfKernel
	mov	si, _LoaderFileName	; ds:si -> "KERNEL  BIN"
	cld

	mov dx, 20h		;;两个扇区有32个目录项
LABEL_SEARCH_FOR_KERNEL_BIN:
	cmp	dx, 0				   ; `. 循环次数控制,
	jz	LABEL_NO_KERNEL_BIN ;  / 如果已经读完了一个 Sector,
	dec	dx				   ; /  就跳到下一个 Sector
	mov	cx, 11
	LABEL_CMP_FILENAME:
		cmp	cx, 0
		jz	LABEL_FILENAME_FOUND	; 如果比较了 11 个字符都相等, 表示找到
		dec	cx
		lodsb				; ds:si -> al
		cmp	al, byte [es:di]
		jz	LABEL_GO_ON
		jmp	LABEL_DIFFERENT		; 只要发现不一样的字符就表明本 DirectoryEntry
					; 不是我们要找的 LOADER.BIN
	LABEL_GO_ON:
		inc	di
		jmp	LABEL_CMP_FILENAME	; 继续循环

LABEL_DIFFERENT:
		and	di, 0FFE0h		;  di &= E0 为了让它指向本条目开头
		add	di, 20h			;       |
		mov	si, _LoaderFileName	;       | di += 20h  下一个目录条目
		jmp	LABEL_SEARCH_FOR_KERNEL_BIN;    /
LABEL_NO_KERNEL_BIN:	
		pop es
		mov ax,	_KernelNoNoFound
		;mov ax, OffsetOfLoader
		Call	DispStr			; 调用显示字符串例程
		jmp	$			; 无限循环
LABEL_FILENAME_FOUND:
;;寻找KERNEL并加载
;;计算文件所在扇区
		and	di, 0FFE0h		; else `. di &= E0 为了让它指向本条目开头
		mov ax, [es:di + 1ah]
		mov word[_ClusterNum], ax
		mov ax, [es:di + 14h]
		mov word[_ClusterNum + 2], ax;得到存放文件的簇号
		
		mov si , OffsetOfKernel
.1:		
		mov  eax, [_ClusterNum]
		cmp  eax, 00FFFFFFFh;判断是否为最后一个文件簇
		je	 .2
		
		xor eax, eax
		mov al, [BPB_SecPerClus]
		mov ebx, [_ClusterNum]
		sub ebx, 2
		mul ebx
		mov ebx, [_SectorNoOfRootDirectory]
		add eax, ebx ;;eax 记录文件扇区位置
		add eax, SectorsOfMBR
		
		push si
		;;读取根目一个簇		
		push eax
		push 8
		push si
		push BaseOfKernel
		call ReadSector
		add  sp , 10
			;打点
		push ax
		push bx
		mov ah, 0Eh
		mov al, '.'
		mov bl, 0Fh
		int 10h
		pop bx
		pop ax
		;计算文件簇号对应的 FAT表 所在的簇号
		xor edx, edx
		xor eax, eax
		mov dx, [_ClusterNum + 2]
		mov ax, [_ClusterNum]
		mov bx, 128 ;一个fat扇区(512B)有128个表 512/4 = 128
		div	bx ;ax 商      dx余数
		
		xor ebx, ebx
		mov bx, word[BPB_RsvdSecCnt]
		add eax, ebx
		add eax, SectorsOfMBR
		
		push eax
		push dx
		;;读取一个fat扇区
		push eax
		push 1
		push 0
		push BaseOfFat
		call ReadSector
		add  sp , 10
		pop dx		
		pop eax
			
		;计算FAT表项内的值	
		push es
		shl  dx, 2   ;2^2 = 4Byte = 一个FAT表大小
		mov  di, dx
		mov  ax, BaseOfFat
		mov  es, ax
		mov  eax, dword[es:di]
		
		pop  es
		pop  si
		add  si, 8*512
		mov  dword[_ClusterNum], eax
		jmp  .1
.2:
		pop es
		
		;jmp BaseOfKernel:OffsetOfKernel
		
;**********************   为转到保护模式做准备  ******************
;初始化32位代码段描述符
		xor	eax, eax
		mov ax, cs
		shl eax, 4
		add eax, LABEL_CODE32
		mov word [LABEL_CODE_DESC + 2], ax
		shr	eax, 16
		mov	byte [LABEL_CODE_DESC + 4], al
		mov byte [LABEL_CODE_DESC + 7], ah
		
		;初始化数据段描述符
		xor	eax, eax
		mov ax, cs
		shl eax, 4
		add eax, LABEL_DATA
		mov word [LABEL_DESCPTR_DATA + 2], ax
		shr	eax, 16
		mov	byte [LABEL_DESCPTR_DATA + 4], al
		mov byte [LABEL_DESCPTR_DATA + 7], ah
		
		;;初始化堆栈段描述符
		xor	eax, eax
		mov ax, cs
		shl eax, 4
		add eax, LABEL_STACK
		mov word [LABEL_DESCPTR_STACK + 2], ax
		shr	eax, 16
		mov	byte [LABEL_DESCPTR_STACK + 4], al
		mov byte [LABEL_DESCPTR_STACK + 7], ah
		
		;为加载GDTR做准备
		xor eax, eax
		mov ax, ds
		shl eax, 4
		add eax, LABEL_GDT_NULL
		mov dword[Gdtptr + 2], eax
		;加载gdtr
		lgdt [Gdtptr]
		;关中断
		cli
		;a20地址线打开
		in al, 92h
		or al, 00000010b
		out 92h, al
		;切换保护模式 准备
		mov eax, cr0
		or	eax, 1
		mov	cr0, eax
		
		;跳转到保护模式
		jmp	dword SelectorCode32:0
;***********************************************************


ReadSector:;;(Base,Offset, sectors_count,sector_num)
	;;读取根目一个簇
		mov ax, word[esp + 2]
		mov word[disk_addr_pkt + buffer_addr+2], ax
		mov ax, [esp + 4]
		mov word[disk_addr_pkt + buffer_addr], ax
		mov ax, [esp + 6]
		mov word[disk_addr_pkt + block_count], ax
		mov ax, [esp + 8]
		mov word[disk_addr_pkt + block_num], ax
		mov ax, [esp + 10]
		mov word[disk_addr_pkt + block_num + 2], ax
		mov byte[disk_addr_pkt + packet_size], 10h
		mov byte[disk_addr_pkt + reserved], 0		
		mov dword[disk_addr_pkt + block_num + 4], 0

		mov ah, 42h
		mov dl, [BS_DrvNum]
		mov si, disk_addr_pkt
		int 13h
	ret
DispStr:
	push bp
	mov bp, ax; ES:BP = 串地址
	mov	cx, 16			; CX = 串长度
	mov	ax, 01301h		; AH = 13,  AL = 01h
	mov	bx, 000ch		; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)
	mov	dx, 0
	int	10h	; int 10h
	pop bp
	ret

[section .gdt] 
;GDT										段基址，     		 段界限，    		 段属性
LABEL_GDT_NULL:				Descriptor 			  0,		   		  0,					0	;空描述符
LABEL_DESCPTR_FLAT_EX:	    Descriptor			  0,			0FFFFFH,		DA_CR  | DA_32 | DA_LIMIT_4K	; 0 ~ 4G	
LABEL_DESCPTR_FLAT_RW:		Descriptor            0,            0FFFFFh, 		DA_DRW | DA_32 | DA_LIMIT_4K	; 0 ~ 4G
LABEL_DESCPTR_VEDIO:		Descriptor	    0B8000H,       		 0FFFFH,	    		    DA_DRW + DA_DPL3	;显存
LABEL_DESCPTR_STACK:		Descriptor			  0,	   TopOfStack-1,					  DA_DRW + DA_32    ;32位堆栈
LABEL_DESCPTR_DATA:			Descriptor			  0,		  DataLen-1,							  DA_DRW
LABEL_CODE_DESC:			Descriptor		      0,		  Code32Len,						DA_C + DA_32	;非一致代码段


;GDT end
Gdtlen						equ 			$ - LABEL_GDT_NULL
Gdtptr						dw				Gdtlen-1; GDTR段界限
							dd				0		;GDTR段基址
;GDT选择子
SelectorFlatEx				equ				LABEL_DESCPTR_FLAT_EX - LABEL_GDT_NULL
SelectorFlatRw				equ				LABEL_DESCPTR_FLAT_RW - LABEL_GDT_NULL
SelectorVedio				equ				LABEL_DESCPTR_VEDIO - LABEL_GDT_NULL + SA_RPL3

SelectorCode32				equ				LABEL_CODE_DESC - LABEL_GDT_NULL
SelectorStack32				equ				LABEL_DESCPTR_STACK - LABEL_GDT_NULL
SelectorData32				equ				LABEL_DESCPTR_DATA - LABEL_GDT_NULL


;end section .gdt
	
	
;数据段
[section .data32]
ALIGN 32
[BITS 32]
LABEL_DATA:
;=================16位模式变量 =============

_DEBUGMSG:					db 		 "Debug .........."
_LoaderFileName:			db		 "KERNEL  BIN", 0 ; KERNEL.BIN 之文件名
_KernelFound:				db  	 "Kernel  Found   "
_KernelNoNoFound:			db		 "Kernel No Found!"
_KernelLoading:				db		 "On      Loading"
_ClusterNum:				dd       0
_SectorNoOfRootDirectory: 	dd 		 0

_SPValueInRealMode			dw 			0
;字符串
_Message: 					db 			"In Protect Mode Now ^-^", 0Ah, 0
_szMemChkTitle:				db			"BaseAddrL BaseAddrH LengthLow LengthHigh   Type", 0Ah, 0	; 进入保护模式后显示此字符串
_szLoadKernelMsg			db			"Load Kernel Done!", 0Ah, 0
;变量
_szRamSize:					db			"RAM Size:", 0
_szReturn					db			0Ah,0
_dwMCRNumber:				dd			0;Memory Check Result
_dwMemSize:					dd			0
_dwDispPos:					dd			(80 * 3 + 0) * 2  ;屏幕显示位置

_ARDStruct:			;Address Range Descriptor Structure
	_dwBaseAddrLow:		dd		0
	_dwBaseAddrHigh:	dd		0
	_dwLengthLow:		dd		0
	_dwLengthHigh:		dd		0
	_dwType:			dd		0
	
_MemChkBuf: 			times	1024	db	0
_dwOffsetOfKenerlEntry		dw	0

;保护模式用以下符号
OffsetMessage			equ			_Message - $$
szReturn				equ			_szReturn - $$
szMemChkTitle			equ			_szMemChkTitle - $$
szRamSize				equ			_szRamSize - $$
szLoadKernelMsg			equ			_szLoadKernelMsg - $$
dwMCRNumber				equ			_dwMCRNumber - $$
dwMemSize				equ			_dwMemSize - $$
dwDispPos				equ			_dwDispPos - $$

ARDStruct				equ			_ARDStruct - $$
	dwBaseAddrLow		equ		_dwBaseAddrLow - $$
	dwBaseAddrHigh		equ		_dwBaseAddrHigh - $$
	dwLengthLow			equ		_dwLengthLow - $$
	dwLengthHigh		equ		_dwLengthHigh - $$
	dwType				equ		_dwType - $$
	
MemChkBuf				equ			_MemChkBuf - $$
dwOffsetOfKernelEntry	equ 	_dwOffsetOfKenerlEntry - $$
DataLen					equ			$ - LABEL_DATA
;;end of .data32

;堆栈段 有时执行出错，堆栈改小后就正常了，Loader大小为4K
[section .stack32]
ALIGN 32
[BITS 32]
LABEL_STACK:
times	512  	db			0
TopOfStack		equ			 $ - LABEL_STACK

;;end of .stack32	
;******************************此处保护模式入口    跳转到内核执行系统代码  ***********
[section .code32]
[BITS 32]
LABEL_CODE32:
		mov	ax, SelectorVedio
		mov	gs, ax
		mov	ax, SelectorStack32
		mov	ss, ax
		mov ax, SelectorData32
		mov ds, ax
		mov ax, SelectorData32
		mov es, ax 
		mov esp, TopOfStack-1
		
		
		;;显示一个字符串
		push	OffsetMessage
		call	DispStrPM 
		add		esp	, 4
		 
		push	szMemChkTitle
		call	DispStrPM
		add		esp, 4
		
		call	DispMemSize	;显示内存信息
		
		
		
		mov		ax, SelectorFlatRw
		mov		ds, ax
		mov		es, ax
		mov		fs, ax
		mov		ss, ax
		mov		esp, 0x30000
		
		;;拷贝内核到某个内存区域
		;jmp 	SelectorFlatEx:(BaseOfKernelFilePhyAddr+OffsetOfKernel)
					
		call	InitKernel 	

		;;显示一个字符串
		mov		ax, SelectorData32
		mov 	ds, ax
		call    DispReturn
		push	szLoadKernelMsg
		call	DispStrPM 
		add		esp	, 4
		
		mov		ax, SelectorFlatRw
		mov		ds, ax
		
		;***************************************************************
		mov 	eax, [BaseOfKernelFilePhyAddr + 18h]; e_pentry
		push	SelectorFlatEx
		push	eax		
		retf
		;jmp    dword SelectorFlatEx:0x30400; 正式进入内核 *
		;***************************************************************
	
		
		;call 	SetupPaging ;启动分页		
		
		jmp	$
; ------------------------------------------------------------------------
; 内存拷贝，仿 memcpy
; ------------------------------------------------------------------------
; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);
; ------------------------------------------------------------------------
MemCpy:
	push	ebp
	mov	ebp, esp
		
	push	esi
	push	edi
	push	ecx

	mov	edi, [ebp + 8]		; Destination
	mov	esi, [ebp + 12]		; Source
	mov	ecx, [ebp + 16]		; Counter
.1:
	cmp	ecx, 0				; 判断计数器
	jz	.2					; 计数器为零时跳出

	mov	al, [ds:esi]		; ┓
	inc	esi					; ┃
							; ┣ 逐字节移动
	mov	byte [es:edi], al	; ┃
	inc	edi					; ┛

	dec	ecx		; 计数器减一
	jmp	.1		; 循环
.2:
	mov	eax, [ebp + 8]	; 返回值

	pop	ecx
	pop	edi
	pop	esi
	mov	esp, ebp
	pop	ebp

	ret			; 函数结束，返回
	
; MemCpy 结束-------------------------------------------------------------

; InitKernel ---------------------------------------------------------------------------------
; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置
; 遍历每一个 Program Header，根据 Program Header 中的信息来确定把什么放进内存，放到什么位置，以及放多少。
; --------------------------------------------------------------------------------------------
InitKernel:
        xor   esi, esi
        mov   cx, word [BaseOfKernelFilePhyAddr+2Ch]; ecx <- pELFHdr->e_phnum
        movzx ecx, cx                               
        mov   esi, [BaseOfKernelFilePhyAddr + 1Ch]  ; esi <- pELFHdr->e_phoff
        add   esi, BaseOfKernelFilePhyAddr			; esi<-OffsetOfKernel+pELFHdr->e_phoff
Begin:		
        mov   eax, [esi + 0]
        cmp   eax, 0                     			 ; PT_NULL
        jz    NoAction

	    push  dword [esi + 010h]      ;size ;-|
        mov   eax, [esi + 04h]            	; |
        add   eax, BaseOfKernelFilePhyAddr	; | memcpy((void*)(pPHdr->p_vaddr),
        push  eax		    		  ;src  ; |      uchCode + pPHdr->p_offset,
        push  dword [esi + 08h]       ;dst  ; |      pPHdr->p_filesz;
        call  MemCpy                        ; |
        add   esp, 12                       ;-|
		
NoAction:

	    add   esi, 020h                   ; esi += pELFHdr->e_phentsize
        dec   ecx				
        jnz   Begin
		
    ret
; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DispMemSize:
		push	esi
		push	edi
		push	ecx
		
		mov		esi, MemChkBuf
		mov		ecx, dword[dwMCRNumber]
.loop:
		mov		edx, 5
		mov		edi, ARDStruct
	.1:
		push	dword[esi]
		call	DispInt
		pop		eax
		stosd		;;填充ARDStruct
		add		esi, 4
		dec		edx
		cmp		edx, 0
		jnz		.1
		call	DispReturn
		
		cmp		dword[dwType], 1
		jne		.2
		mov		eax, [dwBaseAddrLow]
		add		eax, [dwLengthLow]
		cmp		eax, [dwMemSize]
		jb		.2
		mov		[dwMemSize], eax
	.2:
		loop 	.loop
		
		call	DispReturn
		push	szRamSize
		call	DispStrPM
		add		esp, 4
		
		push	dword[dwMemSize]
		call	DispInt
		add		esp, 4
		
		pop		ecx
		pop		edi
		pop		esi
	ret
%include 		"lib.inc"	
Code32Len		equ 		$ - LABEL_CODE32
;end of .code32
